/**
 * File name: UnderstandMetricsReader.java Author: Willard Wang Create date: Jul
 * 17, 2013
 */
package edu.sjtu.se.prophet.io;

import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;

import au.com.bytecode.opencsv.CSVReader;
import edu.sjtu.se.prophet.exception.InvalidDataSourceException;
import edu.sjtu.se.prophet.graph.Graph;
import edu.sjtu.se.prophet.graph.Node;

/**
 * Headers:
 * <ul>
 * <li>Kind</li>
 * <li>Name</li>
 * <li>File</li>
 * <li>AvgCyclomatic</li>
 * <li>AvgCyclomaticModified</li>
 * <li>AvgCyclomaticStrict</li>
 * <li>AvgEssential</li>
 * <li>AvgLine</li>
 * <li>AvgLineBlank</li>
 * <li>AvgLineCode</li>
 * <li>AvgLineComment</li>
 * <li>CountClassBase</li>
 * <li>CountClassCoupled</li>
 * <li>CountClassDerived</li>
 * <li>CountDeclClass</li>
 * <li>CountDeclClassMethod</li>
 * <li>CountDeclClassVariable</li>
 * <li>CountDeclFile</li>
 * <li>CountDeclFunction</li>
 * <li>CountDeclInstanceMethod</li>
 * <li>CountDeclInstanceVariable</li>
 * <li>CountDeclMethod</li>
 * <li>CountDeclMethodAll</li>
 * <li>CountDeclMethodDefault</li>
 * <li>CountDeclMethodPrivate</li>
 * <li>CountDeclMethodProtected</li>
 * <li>CountDeclMethodPublic</li>
 * <li>CountInput</li>
 * <li>CountLine</li>
 * <li>CountLineBlank</li>
 * <li>CountLineCode</li>
 * <li>CountLineCodeDecl</li>
 * <li>CountLineCodeExe</li>
 * <li>CountLineComment</li>
 * <li>CountOutput</li>
 * <li>CountPath</li>
 * <li>CountSemicolon</li>
 * <li>CountStmt</li>
 * <li>CountStmtDecl</li>
 * <li>CountStmtExe</li>
 * <li>Cyclomatic</li>
 * <li>CyclomaticModified</li>
 * <li>CyclomaticStrict</li>
 * <li>Essential</li>
 * <li>MaxCyclomatic</li>
 * <li>MaxCyclomaticModified</li>
 * <li>MaxCyclomaticStrict</li>
 * <li>MaxInheritanceTree</li>
 * <li>MaxNesting</li>
 * <li>PercentLackOfCohesion</li>
 * <li>RatioCommentToCode</li>
 * <li>SumCyclomatic</li>
 * <li>SumCyclomaticModified</li>
 * <li>SumCyclomaticStrict</li>
 * <li>SumEssential</li>
 * </ul>
 * 
 * @author deathspeeder
 * 
 */
public class UnderstandMetricsReader extends IOCommon implements MetricsReader {
    private static Logger logger = Logger
            .getLogger(UnderstandMetricsReader.class);

    static class ReadMetricsThread extends Thread {
        Graph graph;
        String uri;

        public ReadMetricsThread(Graph graph, String uri) {
            this.graph = graph;
            this.uri = uri;
        }

        @Override
        public void run() {
            try {
                read(graph, uri);
            } catch (Exception e) {
                logger.error("Read error", e);
            }
        }
    }

    /**
     * 
     */
    private static String[] VALID_KINDS = new String[] { "Abstract Method",
            "Method", "Private Method", "Private Static Method",
            "Protected Abstract Method", "Protected Method",
            "Protected Static Method", "Public Abstract Method",
            "Public Method", "Public Static Method", "Static Method",
            "Constructor", "Private Constructor", "Public Constructor" };

    private static String[] VALID_METRICS = new String[] { "File",
            "CountInput", "CountLineCode", "CountOutput", "CountPath",
            "Cyclomatic", "Essential", "MaxNesting", "RatioCommentToCode" };

    // private static int[] METRICS_INDEX;

    /*
     * (non-Javadoc)
     * 
     * @see edu.sjtu.se.prophet.io.MetricsReader#init(java.util.List)
     */
    @Override
    public void init(List<Graph> graphs) {
        this.graphs = graphs;
    }

    /*
     * (non-Javadoc)
     * 
     * @see edu.sjtu.se.prophet.io.MetricsReader#addSource(java.lang.String)
     */
    @Override
    public void addSource(String uri) {
        if (uri.endsWith("csv")) {
            uris.add(uri);
        }
    }

    public static void read(Graph graph, String uri)
            throws NumberFormatException, IOException {
        CSVReader reader = new CSVReader(new FileReader(uri));
        String[] nextLine = null;
        boolean isFirstLine = true;
        String[] headers;
        int[] METRICS_INDEX = null;
        while ((nextLine = reader.readNext()) != null) {
            if (isFirstLine) {
                headers = nextLine;
                METRICS_INDEX = new int[VALID_METRICS.length];
                for (int i = 0; i < VALID_METRICS.length; i++) {
                    for (int j = 0; j < headers.length; j++) {
                        if (VALID_METRICS[i].equals(headers[j]))
                            METRICS_INDEX[i] = j;
                    }
                }
                isFirstLine = false;
            } else {
                // check it is a method line
                if (contains(VALID_KINDS, nextLine[0])) {
                    boolean match = false;
                    for (Iterator<Node> it = graph.getNodes().iterator(); it
                            .hasNext();) {
                        Node node = it.next();
                        if (fuzzyMatch(node.getId(), nextLine[1])) {
                            for (int i = 0; i < VALID_METRICS.length; i++) {
                                String val = nextLine[METRICS_INDEX[i]];
                                if ("File".equals(VALID_METRICS[i])) {
                                    node.getMetrics()
                                            .put(VALID_METRICS[i], val);
                                } else {
                                    if (null == val || val.trim().equals(""))
                                        val = "0";
                                    node.getMetrics().put(VALID_METRICS[i],
                                            Float.parseFloat(val));
                                }
                            }
                            match = true;
                            logger.info("Matched method: " + nextLine[1]
                                    + " on " + node.getId());
                            break;
                        }
                    }

                    if (!match)
                        logger.info("Ignored method: " + nextLine[1]
                                        + " on version "
                                        + graph.getMeta().get(VERSION));
                }
            }
        }
        reader.close();
    }

    /*
     * (non-Javadoc)
     * 
     * @see edu.sjtu.se.prophet.io.MetricsReader#read()
     */
    @Override
    public void read() throws IOException, InvalidDataSourceException {
        List<Thread> threads = new ArrayList<Thread>();
        for (String uri : uris) {
            Graph graph = getGraphOfVersion(getVersion(uri));
            if (graph == null)
                throw new RuntimeException("No graph found for URI " + uri);
            Thread t = new ReadMetricsThread(graph, uri);
            threads.add(t);
            t.start();
        }

        try {
            for (Thread t : threads) {
                t.join();
            }
        } catch (InterruptedException e) {
            logger.error("Not expected!", e);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see edu.sjtu.se.prophet.io.MetricsReader#read()
     */
    // @Override
    // public void read() throws IOException, InvalidDataSourceException {
    // for (String uri : uris) {
    // Graph graph = getGraphOfVersion(getVersion(uri));
    // if (graph == null)
    // throw new RuntimeException("No graph found for URI " + uri);
    // CSVReader reader = new CSVReader(new FileReader(uri));
    // String[] nextLine = null;
    // boolean isFirstLine = true;
    // String[] headers;
    // while ((nextLine = reader.readNext()) != null) {
    // if (isFirstLine) {
    // headers = nextLine;
    // METRICS_INDEX = new int[VALID_METRICS.length];
    // for (int i = 0; i < VALID_METRICS.length; i++) {
    // for (int j = 0; j < headers.length; j++) {
    // if (VALID_METRICS[i].equals(headers[j]))
    // METRICS_INDEX[i] = j;
    // }
    // }
    // isFirstLine = false;
    // } else {
    // // check it is a method line
    // if (contains(VALID_KINDS, nextLine[0])) {
    // boolean match = false;
    // for (Iterator<Node> it = graph.getNodes().iterator(); it
    // .hasNext();) {
    // Node node = it.next();
    // if (fuzzyMatch(node.getId(), nextLine[1])) {
    // for (int i = 0; i < VALID_METRICS.length; i++) {
    // String val = nextLine[METRICS_INDEX[i]];
    // if ("File".equals(VALID_METRICS[i])) {
    // node.getMetrics().put(VALID_METRICS[i],
    // val);
    // } else {
    // if (null == val
    // || val.trim().equals(""))
    // val = "0";
    // node.getMetrics().put(VALID_METRICS[i],
    // Float.parseFloat(val));
    // }
    // }
    // match = true;
    // System.out.println("Matched method: "
    // + nextLine[1] + " on " + node.getId());
    // break;
    // }
    // }
    //
    // if (!match)
    // System.out.println("Ignored method: " + nextLine[1]
    // + " on version "
    // + graph.getMeta().get(VERSION));
    // }
    // }
    // }
    // reader.close();
    // }
    // }
    /**
     * @param set
     * @param string
     * @return true if set contains string
     */
    private static boolean contains(String[] set, String string) {
        if (string == null)
            return false;

        for (String s : set) {
            if (string.equals(s))
                return true;
        }
        return false;
    }

    /**
     * Extract version string from uri path. e.g. return 1.0 for uri
     * '/home/deathspeeder/1.0.csv'
     * 
     * @param uri
     * @return
     */
    private String getVersion(String uri) {
        if (null == uri)
            return null;
        String[] tokens = uri.split("/");
        if (tokens.length > 0) {
            return tokens[tokens.length - 1].replace(".csv", "");
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see edu.sjtu.se.prophet.io.SettableIO#setValue(java.lang.String,
     * java.lang.String)
     */
    @Override
    public void setValue(String key, String value) {
        if ("files".equals(key)) {
            for (String str : value.split("[,;:]")) {
                addSource(str);
            }
        }
    }

}
